package ue.networking;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.net.Socket;
import java.util.Arrays;

import ue.core.Galaxy;
import ue.galaxy.Asteroid;
import ue.galaxy.Location;
import ue.resources.Deposit;

/**
 * 
 * @author ruben.ortlam
 *
 */
public class Client {
	public enum Status {
		UNIDENTIFIED, IDENTIFIED, DISCONNECTED
	}
	
	private int id;
	
	public Socket socket;
	public long lastPing;
	
	public ClientInputListener in;
	public DataOutputStream out;
	
	public Thread inThread;
	
	private Status status;
	private String name;
	private String password;
	
	/**
	 * Creates a Client with the given Socket.
	 *
	 * @param socket The Socket holding the connection to the client.
	 */
	public Client(Socket socket, int id) {
		this.socket = socket;
		this.id = id;
		try {
			out = new DataOutputStream(socket.getOutputStream());
			out.flush();
			in = new ClientInputListener(new DataInputStream(socket.getInputStream()), this);
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		lastPing = System.currentTimeMillis();
		
		status = Status.UNIDENTIFIED;
		
		inThread = new Thread(in);
		inThread.start();
	}
	
	public void run() {
		if(System.currentTimeMillis() - lastPing >= 2000) {
			sendCommand((byte)0);
		}
		
		if(hasTimedOut()) {
			System.out.println(Galaxy.getTimestamp() + "Client " + id + " timed out");
			disconnect();
		}
	}
	
	protected void sendCommand(byte command) {
		try {
			out.write(new byte[] { command });
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	protected void sendBigInteger(BigInteger n) {
		try {
			out.write(Arrays.copyOf(n.toByteArray(), 64));
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public void sendLocation(Location location) {
		sendCommand((byte)4);
		
		try {
			out.writeInt(location.getX());
			out.writeInt(location.getY());
//			out.writeLong(location.getSeed());
			out.writeDouble(location.getSunFactor());
			out.writeInt(location.getAsteroidAmount());
			out.writeDouble(location.getAsteroidValue());
			for(int i = 0; i < location.getAsteroidAmount(); i++) {
				sendAsteroid(location.getAsteroids().get(i));
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public void sendAsteroid(Asteroid asteroid) {
		try {
			out.writeInt(asteroid.getDuration());
			out.writeDouble(asteroid.getValue());
			out.writeInt(asteroid.getMainResource());
			sendDeposit(asteroid.getDeposit());
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public void sendDeposit(Deposit deposit) {
		try {
			out.writeInt(Deposit.LENGTH);
			for(int i = 0; i < Deposit.LENGTH; i++) {
				out.writeInt(deposit.getResource(i));
			}
		} catch (IOException e) {
			
		}
	}

	public void disconnect() {
		try {
			out.close();
			in.close();
			
			socket.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		try {
			inThread.join(100);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		status = Client.Status.DISCONNECTED;
	}
	
	/**
	 * Returns if the client has disconnected.
	 *
	 * @return true if the client is disconnected, false otherwise
	 */
	public boolean hasTimedOut() {
//		System.out.println(lastPing + ":" + System.currentTimeMillis() + ":" + (System.currentTimeMillis() - lastPing));
		if(System.currentTimeMillis() - lastPing >= 3000) {
			return true;
		}
		return false;
	}
	
	public Status getStatus() {
		return status;
	}
	public void setStatus(Status status) {
		this.status = status;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}

	public int getID() {
		return id;
	}
}
